import {
  useMemo,
  useReducer,
  useRef,
  useState,
  useCallback,
  useLayoutEffect,
} from "react";
import type { RefObject } from "react";

export type ElementType = Element | HTMLElement | null;
export type RefElementType = RefObject<ElementType>;
export type FunctionElementType = () => ElementType;
export type ParamType = RefElementType | FunctionElementType;
export type PositionType = Partial<{
  x: number;
  y: number;
  isMove: boolean;
  maxX: number;
  maxY: number;
  minX: number;
  minY: number;
  centerX: number;
  centerY: number;
}>;
export type OptionType = Partial<{
  isLimitX: boolean;
  isLimitY: boolean;
  defaultPosition: {
    x: number;
    y: number;
  };
}>;
const useAnyDrag = (
  el: ParamType,
  option: OptionType = {
    isLimitX: true,
    isLimitY: true,
    defaultPosition: { x: 0, y: 0 },
  },
  containerRef?: ParamType
): PositionType => {
  const isMobile = navigator.userAgent.match(/(iPhone|iPod|Android|ios)/i);
  const eventType = isMobile
    ? ["touchstart", "touchmove", "touchend"]
    : ["mousedown", "mousemove", "mouseup"];
  const element = useRef<ElementType>();
  const containerElement = useRef<ElementType>();
  const { isLimitX, isLimitY, defaultPosition } = option;
  const globalWidthHeight = {
    offsetWidth: window.innerWidth,
    offsetHeight: window.innerHeight,
  };
  let isStart = false;
  const [position, setPosition] = useState<PositionType>({
    x: defaultPosition?.x,
    y: defaultPosition?.y,
    maxX: 0,
    maxY: 0,
    centerX: 0,
    centerY: 0,
    minX: 0,
    minY: 0,
  });
  const [isMove, setIsMove] = useState(false);
  const downPosition = {
    x: 0,
    y: 0,
  };
  const setOverflow = () => {
    const limitEle = (containerElement.current || document.body) as HTMLElement;
    if (isLimitX) {
      limitEle.style.overflowX = "hidden";
    } else {
      limitEle.style.overflowX = "";
    }
    if (isLimitY) {
      limitEle.style.overflowY = "hidden";
    } else {
      limitEle.style.overflowY = "";
    }
  };
  const onStartHandler = useCallback((e: Event) => {
    isStart = true;
    const target = element.current as HTMLElement;
    if (target) {
      target.style.cursor = "move";
    }
    const event: Touch | MouseEvent =
      e instanceof TouchEvent ? e.changedTouches[0] : (e as MouseEvent);
    const { clientX, clientY } = event;
    downPosition.x = clientX - target.offsetLeft;
    downPosition.y = clientY - target.offsetTop;
    setOverflow();
    window.addEventListener(eventType[1], onMoveHandler);
    window.addEventListener(eventType[2], onUpHandler);
  }, []);
  const onMoveHandler = useCallback((e: Event) => {
    if (!isStart) {
      return;
    }
    setOverflow();
    const event: Touch | MouseEvent =
      e instanceof TouchEvent ? e.changedTouches[0] : (e as MouseEvent);
    const { clientX, clientY } = event;
    if (!element.current) {
      return;
    }
    const { offsetWidth, offsetHeight } = element.current as HTMLElement;
    const { offsetWidth: containerWidth, offsetHeight: containerHeight } =
      (containerElement.current as HTMLElement) || globalWidthHeight;
    const { x, y } = downPosition;
    const moveX = clientX - x,
      moveY = clientY - y;
    const data = {
      x: isLimitX
        ? Math.max(0, Math.min(containerWidth - offsetWidth, moveX))
        : moveX,
      y: isLimitY
        ? Math.max(0, Math.min(containerHeight - offsetHeight, moveY))
        : moveY,
      minX: 0,
      minY: 0,
      maxX: containerWidth - offsetWidth,
      maxY: containerHeight - offsetHeight,
      centerX: (containerWidth - offsetWidth) / 2,
      centerY: (containerHeight - offsetHeight) / 2,
    };
    setIsMove(true);
    setPosition(data);
  }, []);
  const onUpHandler = useCallback(() => {
    const target = element.current as HTMLElement;
    if (target) {
      target.style.cursor = "";
    }
    isStart = false;
    setIsMove(false);
    const limitEle = (containerElement.current || document.body) as HTMLElement;
    limitEle.style.overflowX = "";
    limitEle.style.overflowY = "";
    window.removeEventListener(eventType[1], onMoveHandler);
    window.removeEventListener(eventType[2], onUpHandler);
  }, []);
  useLayoutEffect(() => {
    element.current = typeof el === "function" ? el() : el.current;
    containerElement.current =
      typeof containerRef === "function"
        ? containerRef()
        : containerRef?.current;
    if (!element.current) {
      return;
    }
    element.current.addEventListener(eventType[0], onStartHandler);
    return () => {
      element.current?.removeEventListener(eventType[0], onStartHandler);
    };
  }, []);
  return {
    ...position,
    isMove,
  };
};

const useGetSet = <T>(initialState: T): [() => T, (s: T) => void] => {
  const state = useRef<T>(initialState);
  const [, update] = useReducer(() => Object.create(null), {});
  const updateState = (newState: T) => {
    state.current = newState;
    update();
  };
  return useMemo(() => [() => state.current, updateState], []);
};
const useIsScroll = <T extends HTMLElement>(
  el: Window | T | (() => T) = window,
  throlleTime: number = 300
): {
  isScroll: boolean;
  scrollTop: number;
} => {
  const [isScroll, setIsScroll] = useGetSet(false);
  const [scrollTop, setScrollTop] = useGetSet(0);
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const onScrollHandler = useCallback(() => {
    setIsScroll(true);
    setScrollTop(window.scrollY);
    if (timer.current) {
      clearTimeout(timer.current);
    }
    timer.current = setTimeout(() => {
      setIsScroll(false);
    }, throlleTime);
  }, []);
  useLayoutEffect(() => {
    const ele = typeof el === "function" ? (el as () => T)() : el;
    if (!ele) {
      return;
    }
    ele.addEventListener("scroll", onScrollHandler, false);
    return () => {
      ele.removeEventListener("scroll", onScrollHandler, false);
    };
  }, []);
  return {
    isScroll: isScroll(),
    scrollTop: scrollTop(),
  };
};
export {
    useAnyDrag,
    useIsScroll,
    useGetSet
}
